import "./style.css";
import Sortable from "sortablejs";


/**
 * SortableJS 实现拖拽窗口栏移动元素位置
 * @param movableContainer - 拖拽容器元素
 * @param handleClass - 拖拽把手元素的类名，注意：类名**需要**加上`.`符号
 * @param ghostClass - 幽灵元素的类名，注意：类**不需要**加上`.`符号
 * @param fallbackClass - 回退元素的类名，注意：类名**不需要**加上`.`符号
 */
const moveByDragHeader = (
    movableContainer: HTMLElement,
    handleClass?: string,
    ghostClass?: string,
    fallbackClass?: string
) => {
    let offsetX: number, offsetY: number;
    Sortable.create(movableContainer, {
        sort: false,
        handle: handleClass,
        ghostClass,
        forceFallback: true,
        fallbackClass,
        onStart: e => {
            const pointerEvent = (e as any).originalEvent as PointerEvent;
            offsetX = pointerEvent.offsetX;
            offsetY = pointerEvent.offsetY;
        },
        onEnd: e => {
            const pointerEvent = (e as any).originalEvent as PointerEvent;
            const left = pointerEvent.clientX - offsetX;
            const top = pointerEvent.clientY - offsetY;
            e.item.style.left = `${left}px`;
            e.item.style.top = `${top}px`;
        }
    });
};

/**
 * 模拟桌面端拖拽调整窗口大小的方式，调整元素大小
 * 在容器元素周围添加边缘元素结合鼠标事件实现。
 */

/**
 * 根据拖拽的边缘元素的位置，返回对应的调整元素大小的 mousemove 事件处理函数
 * @param containerEl - 容器元素
 * @param position - 拖拽元素的位置
 * @param minWidth - 最小宽度
 * @param minHeight - 最小高度
 * @returns 对应位置调整元素大小的函数
 */
const moveFactory = (
    containerEl: HTMLElement,
    position: "left" | "right" | "top" | "bottom",
    minWidth = 300,
    minHeight = 200
) => {
    const handlerMap = {
        left: (e: MouseEvent) => {
            // 实时计算元素移动的微小距离
            const { left, width } = containerEl.getBoundingClientRect();
            const moveDistance = left - e.clientX; // 移动距离，往左是正数，往右是负数

            // 实时计算元素的新宽度
            const newWidth = width + moveDistance;
            containerEl.style.width = `${Math.max(minWidth, newWidth)}px`;
            // 左边缘拖拽要注意：不仅要设置新的宽度，还要设置新的左侧距离。要不然元素宽度的生长会在右侧
            // 拖拽顶部边缘元素同理。
            // 左边缘和下边缘则不需要，因为拖拽它们，本来就相对的是左上角，而元素默认生长方向就是从左上角开始。
            // 所以这两个位置直接计算宽度高度即可。
            containerEl.style.left = `${Math.max(0, e.clientX)}px`;
        },
        right: (e: MouseEvent) => {
            const { left } = containerEl.getBoundingClientRect();
            const newWidth = e.clientX - left; // 鼠标视口位置相对左上角直接计算新的宽度
            containerEl.style.width = `${Math.max(minWidth, newWidth)}px`;
        },
        top: (e: MouseEvent) => {
            const { top, height } = containerEl.getBoundingClientRect();
            const moveDistance = top - e.clientY; // 移动距离，往上是正数，往下是负数
            const newHeight = height + moveDistance;
            containerEl.style.height = `${Math.max(minHeight, newHeight)}px`;
            containerEl.style.top = `${Math.max(0, e.clientY)}px`;
        },
        bottom: (e: MouseEvent) => {
            const { top } = containerEl.getBoundingClientRect();
            const newHeight = e.clientY - top;
            containerEl.style.height = `${Math.max(minHeight, newHeight)}px`;
        }
    };
    return handlerMap[position];
};

/**
 * 拖拽容器元素的四条边元素去调整元素大小
 * @param containerEl - 容器元素
 * @param edgeEL - 拖拽的边元素
 * @param edgeELPosition - 边元素的位置
 * @param minWidth - 最小宽度
 * @param minHeight - 最小高度
 */
const resizeByDragEdge = (
    containerEl: HTMLElement,
    edgeEL: HTMLElement,
    edgeELPosition: "left" | "right" | "top" | "bottom",
    minWidth = 300,
    minHeight = 200
) => {
    // 鼠标事件实现拖拽固定结构
    edgeEL.addEventListener("mousedown", () => {
        document.addEventListener("mousemove", handleMouseMove);
        document.addEventListener("mouseup", clearListener);
    });

    const handleMouseMove = (e: MouseEvent) => moveFactory(containerEl, edgeELPosition, minWidth, minHeight)(e);

    function clearListener() {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", clearListener);
    }
};

/**
 * 拖拽容器元素的四个角元素去调整元素大小
 * @param containerEl - 容器元素
 * @param cornerEL - 拖拽的角元素
 * @param cornerELPosition - 角元素的位置
 * @param minWidth - 最小宽度
 * @param minHeight - 最小高度
 */
const resizeByDragCorner = (
    containerEl: HTMLElement,
    cornerEL: HTMLElement,
    cornerELPosition: "left-top" | "right-top" | "left-bottom" | "right-bottom",
    minWidth = 300,
    minHeight = 200
) => {
    // 鼠标事件实现拖拽固定结构
    cornerEL.addEventListener("mousedown", () => {
        document.addEventListener("mousemove", handleMouseMove);
        document.addEventListener("mouseup", clearListener);
    });

    const handleMouseMove = (e: MouseEvent) => {
        // 调用对应位置的moveFactory函数
        // 拖动一个角，也就是同时调整两个方向的大小，调用两个对应方向的处理函数即可
        cornerELPosition.split("-").forEach((position: any) => {
            moveFactory(containerEl, position, minWidth, minHeight)(e);
        });
    };

    function clearListener() {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", clearListener);
    }
};

// 调用 SortableJS 实现拖拽窗口栏移动元素位置
moveByDragHeader(document.body, ".drag-bar", "drag-bar-ghost", "drag-bar-fallback");

const container = document.querySelector(".container") as HTMLDivElement;

// 边元素拖拽调整元素大小
const edgeRight = document.querySelector(".edge-right") as HTMLDivElement;
const edgeLeft = document.querySelector(".edge-left") as HTMLDivElement;
const edgeTop = document.querySelector(".edge-top") as HTMLDivElement;
const edgeBottom = document.querySelector(".edge-bottom") as HTMLDivElement;
resizeByDragEdge(container, edgeRight, "right");
resizeByDragEdge(container, edgeLeft, "left");
resizeByDragEdge(container, edgeTop, "top");
resizeByDragEdge(container, edgeBottom, "bottom");

// 角元素拖拽调整元素大小
resizeByDragCorner(container, document.querySelector(".corner-rb") as HTMLDivElement, "right-bottom");
resizeByDragCorner(container, document.querySelector(".corner-rt") as HTMLDivElement, "right-top");
resizeByDragCorner(container, document.querySelector(".corner-lt") as HTMLDivElement, "left-top");
resizeByDragCorner(container, document.querySelector(".corner-lb") as HTMLDivElement, "left-bottom");
